Day 24: Introduction to Spring Boot
Spring Boot is the de facto standard framework for Java web application development. It lets you build applications quickly without complex configuration. Using a restaurant analogy, Spring is the kitchen equipment set, while Spring Boot is a turnkey kitchen with everything already set up.
Starting a Spring Boot Project
Generate a project via Spring Initializr (https://start.spring.io) or configure it manually.
// build.gradle.kts
/*
plugins {
java
id("org.springframework.boot") version "3.2.0"
id("io.spring.dependency-management") version "1.1.4"
}
group = "com.example"
version = "0.0.1-SNAPSHOT"
java {
sourceCompatibility = JavaVersion.VERSION_17
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
tasks.test {
useJUnitPlatform()
}
*/
// Main application class
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // Auto-config + component scanning + config class
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
// Embedded Tomcat server starts on port 8080
}
}
IoC/DI (Inversion of Control and Dependency Injection)
Understanding Spring’s core principle. The Spring container manages object creation and lifecycle instead of the developer.
import org.springframework.stereotype.Service;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
// Interface definition
interface GreetingService {
String greet(String name);
}
// Implementation 1: Register as a Spring Bean with @Service
@Service
class KoreanGreetingService implements GreetingService {
@Override
public String greet(String name) {
return "Hello, " + name + "!";
}
}
// Implementation 2 (select with @Primary or @Qualifier when needed)
// @Service
// class EnglishGreetingService implements GreetingService {
// @Override
// public String greet(String name) {
// return "Hello, " + name + "!";
// }
// }
// Class that receives dependency injection
@Component
class WelcomeProcessor {
private final GreetingService greetingService;
// Constructor injection (recommended approach)
// @Autowired can be omitted (when there's only one constructor)
WelcomeProcessor(GreetingService greetingService) {
this.greetingService = greetingService;
}
String processWelcome(String name) {
String greeting = greetingService.greet(name);
return "[Welcome] " + greeting;
}
}
// Benefits of DI:
// 1. Loose coupling: easy to swap implementations
// 2. Testability: can inject mock objects
// 3. Code reuse: same bean used in multiple places
Simple Web Controller
Create a controller that handles HTTP requests.
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import java.time.LocalDateTime;
import java.util.Map;
@RestController // Controller that returns JSON responses
@RequestMapping("/api") // Common path prefix
class HelloController {
private final GreetingService greetingService;
HelloController(GreetingService greetingService) {
this.greetingService = greetingService;
}
// GET /api/hello
@GetMapping("/hello")
Map<String, Object> hello() {
return Map.of(
"message", "Hello, Spring Boot!",
"timestamp", LocalDateTime.now().toString()
);
}
// GET /api/hello/Alice
@GetMapping("/hello/{name}")
Map<String, String> helloName(@PathVariable String name) {
return Map.of(
"greeting", greetingService.greet(name)
);
}
// GET /api/search?keyword=java&page=1
@GetMapping("/search")
Map<String, Object> search(
@RequestParam String keyword,
@RequestParam(defaultValue = "1") int page) {
return Map.of(
"keyword", keyword,
"page", page,
"results", "Search results for '" + keyword + "' (page " + page + ")"
);
}
// POST /api/echo
@PostMapping("/echo")
ResponseEntity<Map<String, Object>> echo(@RequestBody Map<String, Object> body) {
return ResponseEntity.ok(Map.of(
"received", body,
"echo", true,
"timestamp", LocalDateTime.now().toString()
));
}
}
application.properties Configuration
The Spring Boot application configuration file.
// src/main/resources/application.properties
/*
# Server settings
server.port=8080
server.servlet.context-path=/
# Logging settings
logging.level.root=INFO
logging.level.com.example=DEBUG
# JSON settings
spring.jackson.serialization.indent-output=true
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
# Database (H2 in-memory)
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# Enable H2 console (for development)
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
*/
// application.yml format (alternative):
/*
server:
port: 8080
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
logging:
level:
root: INFO
com.example: DEBUG
*/
// Profile-specific settings:
// application-dev.properties (development)
// application-prod.properties (production)
// Activation: spring.profiles.active=dev
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
class AppConfig {
@Value("${server.port:8080}")
private int port;
@Value("${app.name:MyApp}")
private String appName;
void printConfig() {
System.out.println("Port: " + port);
System.out.println("App name: " + appName);
}
}
Today’s Exercises
-
Profile API: Create a
/api/profileendpoint. GET returns profile info (name, email, bio), and POST updates the profile. Store data in memory (Map). -
Currency Exchange API: Create a controller that handles GET requests in the form
/api/exchange?from=USD&to=KRW&amount=100. Hardcode exchange rate data in a Map, and return an appropriate error response for unsupported currencies. -
DI Practice: Create a
NotificationServiceinterface withEmailNotificationandSmsNotificationimplementations, and write a controller that uses@Qualifierto inject the desired implementation.